import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * <p>Title: CGIHandler.java</p>
 *
 * <p>Description: Used to handle cgi script requests. It will create a runnable
 * thread which will execute the script on the server and read the output from
 * that script, which will then be sent back to the client.</p>
 *
 * <p>Copyright: Copyright (c) 2005</p>
 *
 * <p>Company: </p>
 * @author David Beaver and Aishwarya Iyer
 * @version 1.0
 */

public class CGIHandler {
    private Request clientRequest;
    private Environment env;
    //public static Buffer output = new Buffer();
    //public static boolean done = false;

    /**
    * Constructor for CGIHandler class. Used to save the variables necessary for
    * the cgi script to run properly and for the response to be sent back to the
    * client.
    *
    * @param rq Request variable which correlates to the request that was sent
    *   from the client to the server.
    * @param ev Environment variable used to get the state of the server
    *   environment. This information should have been saved when the server was
    *   configured.
    */
    public CGIHandler(Request rq, Environment ev) {
        this.clientRequest = rq;
        this.env = ev;
    }

    /**
    * Function used to execute the cgi script that was being requested by the
    * client.
    *
    * HINT: Try using the Runtime.getRuntime().exec() function to execute
    * the script. Also look into using both Data and Buffered Streams.
    *
    * @return BufferedInputStream which is the output from the script execution.
    *   This is sent back to the server and then back to the client.
    */

    public String runScript() {
        String directory = "";
        StringTokenizer directoryParser = new StringTokenizer(this.clientRequest.getCGI(),"/"); //find the directory that the cgi file is contained in
        while(directoryParser.countTokens() > 1){ //while there are more folders to traverse
            directory += directoryParser.nextToken() + File.separator; //add it to the file path
        }
        String cgiFile = directoryParser.nextToken(); //save the anem of the CGI file
        ProcessBuilder cgi = new ProcessBuilder("perl.exe",cgiFile); //create a new Process that will launch the CGI
        Map<String, String> environment = cgi.environment(); //retrieve the process' environment for editing
        HashMap<String,String> newEnvironment = this.env.getEnv(); //retrieve new environemtn to add elements to this environment
        Set<String> variables = newEnvironment.keySet(); //retrieve all the keys in the new environment
        String value = "";
        for(String var : variables){ //for each key in the new environment
            value = newEnvironment.get(var); //get its value
            if(!value.isEmpty()){ //if it has a value
                environment.put(var, newEnvironment.get(var)); //add it to the environment
            }
        }
        cgi.directory(new File(directory)); //set the working directory of the CGI to the CGI directory
        cgi.redirectErrorStream(true); //set the stderr to be output in the stdout of the process
        BufferedInputStream cgiOutput = null; //create BufferedInputStream for the CGI output
        String out = "";
        try {
            Process cgiRun = cgi.start(); //start the CGI process
            cgiOutput = new BufferedInputStream(cgiRun.getInputStream()); //retrieve the stdout of the process
            new Thread( new Receiver( cgiOutput ) ).start(); //start the Receiver thread to read the stdout of the process
            try
            {
                cgiRun.waitFor(); //wait for the CGI to finish
                cgiRun.getErrorStream().close(); //close stderr
                //while(CGIHandler.done == false){}
                while(Receiver.isDone == false){} //wait for the Receiver to finish reading CGI output
                //out = CGIHandler.output.getOutput();
                out = Receiver.output.getOutput(); //save the output
                //CGIHandler.output = new Buffer();
            }
            catch ( InterruptedException e )
            {
                Thread.currentThread().interrupt();
            }
            //out.println( "Child done" );
            // at this point the child is complete.  All of its output may or may not have been processed however.
            // The Receiver thread will continue until it has finished processing it.
            // You must close the streams even if you never use them!  In this case the threads close is and os.
            } catch (IOException ex) {
                Logger.getLogger(CGIHandler.class.getName()).log(Level.SEVERE, null, ex);
            }
        return out; //return the CGI output
    }
}

class Buffer{
    public StringBuilder output;

    public Buffer(){
        this.output = new StringBuilder();
    }

    public String getOutput(){
        return this.output.toString();
    }

    public synchronized void write(String line){
        this.output.append(line).append("\n");
    }
}

class Receiver implements Runnable
{
    // ------------------------------ FIELDS ------------------------------

    /**
     * stream to receive data from child
     */
    public static Buffer output = new Buffer(); //global Buffer with which to process CGI output
    public static boolean isDone = false; //global flag indicated whether the Reciever is finished
    private final InputStream is; //client output stream

    // -------------------------- PUBLIC INSTANCE  METHODS --------------------------

    /**
     * method invoked when Receiver thread started.  Reads data from child and displays in on System.out.
     */
    public void run()
    {
        try
            {
            final BufferedReader br = new BufferedReader( new InputStreamReader( is ), 50 /* keep small for testing */ ); //create a BufferedReader to read the CGI output
            String line;
            while ( ( line = br.readLine() ) != null ) //while there is still output
            {
                Receiver.output.write(line); //save the output in the Buffer
                //CGIHandler.output.write(line);
            }
            //finished reading CGI otuput
            br.close(); //close the reader
        }
        catch ( IOException e )
        {
            throw new IllegalArgumentException( "IOException receiving data from child process." );
        }
        //CGIHandler.done = true;
        Receiver.isDone = true; //signal that the Reciever is finished
    }

    // --------------------------- CONSTRUCTORS ---------------------------

    /**
     * contructor
     *
     * @param is stream to receive data from child
     */
    Receiver( InputStream is )
    {
        this.is = is;
    }
}

